﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Dynamic;
using System.Xml.Linq;
using System.Xml;
using System.IO;

using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;
using AttributeData = Roslyn.Compilers.CSharp.AttributeData;

using King.Reflection;
using King.Extensions;
using King.Collections;
using King.CSharp.Text;

namespace King.CSharp.Reflection {

    public static partial class CSharpSymbolExtensions {

        internal static string GetCSharpName(this TypeName typeName, out int arity) {
            var name = typeName.Name;

            arity = 0;

            // parse for arity
            var match = Regex.Match(name, @"^(?<name>[^`]*)`(?<arity>\d*)$");
            if (match.Success) {
                name = match.Get("name");
                arity = match.Get<int>("arity");
            }

            return name;
        }

        public static IEnumerable<dynamic> SyntheticValues(this IEnumerable<CSharpAttributeSymbol> attributes) {
            return attributes.Select(o => o.GetSyntheticValue());
        }
        public static dynamic GetSyntheticValue(this CSharpAttributeSymbol attributeData) {
            return new CSharpSyntheticAttribute(attributeData);
        }

        public static CSharpTypeSymbol TryGetType(this CSharpCompilation compilation, Type type) {
            return compilation.TryGetType(type.AssemblyQualifiedName);
        }
        public static CSharpTypeSymbol GetType(this CSharpCompilation compilation, Type type) {
            return compilation.GetType(type.AssemblyQualifiedName);
        }

        public static CSharpTypeSymbol TryGetType(this CSharpAssemblySymbol assembly, Type type) {
            return assembly.TryGetType(type.AssemblyQualifiedName);
        }
        public static CSharpTypeSymbol GetType(this CSharpAssemblySymbol assembly, Type type) {
            return assembly.GetType(type.AssemblyQualifiedName);
        }

        public static IEnumerable<CSharpAttributeSymbol> Attributes(this CSharpAssemblySymbol symbol, Type type = null) {
            return symbol.Attributes(symbol.Compilation.GetType(type));
        }
        public static CSharpAttributeSymbol GetAttribute(this CSharpAssemblySymbol symbol, Type type = null) {
            return symbol.GetAttribute(symbol.Compilation.GetType(type));
        }
        public static IEnumerable<dynamic> SyntheticAttributes(this CSharpAssemblySymbol symbol, Type type = null) {
            return symbol.SyntheticAttributes(symbol.Compilation.GetType(type));
        }
        public static dynamic GetSyntheticAttribute(this CSharpAssemblySymbol symbol, Type type) {
            return symbol.GetSyntheticAttribute(symbol.Compilation.GetType(type));
        }
        public static bool IsDefined(this CSharpAssemblySymbol symbol, Type type) {
            return symbol.IsDefined(symbol.Compilation.GetType(type));
        }

        public static IEnumerable<CSharpAttributeSymbol> Attributes(this CSharpModuleSymbol symbol, Type type = null) {
            return symbol.Attributes(symbol.Compilation.GetType(type));
        }
        public static CSharpAttributeSymbol GetAttribute(this CSharpModuleSymbol symbol, Type type = null) {
            return symbol.GetAttribute(symbol.Compilation.GetType(type));
        }
        public static IEnumerable<dynamic> SyntheticAttributes(this CSharpModuleSymbol symbol, Type type = null) {
            return symbol.SyntheticAttributes(symbol.Compilation.GetType(type));
        }
        public static dynamic GetSyntheticAttribute(this CSharpModuleSymbol symbol, Type type) {
            return symbol.GetSyntheticAttribute(symbol.Compilation.GetType(type));
        }
        public static bool IsDefined(this CSharpModuleSymbol symbol, Type type) {
            return symbol.IsDefined(symbol.Compilation.GetType(type));
        }

        public static IEnumerable<CSharpAttributeSymbol> Attributes(this CSharpFieldSymbol symbol, Type type = null) {
            return symbol.Attributes(symbol.Compilation.GetType(type));
        }
        public static CSharpAttributeSymbol GetAttribute(this CSharpFieldSymbol symbol, Type type = null) {
            return symbol.GetAttribute(symbol.Compilation.GetType(type));
        }
        public static IEnumerable<dynamic> SyntheticAttributes(this CSharpFieldSymbol symbol, Type type = null) {
            return symbol.SyntheticAttributes(symbol.Compilation.GetType(type));
        }
        public static dynamic GetSyntheticAttribute(this CSharpFieldSymbol symbol, Type type) {
            return symbol.GetSyntheticAttribute(symbol.Compilation.GetType(type));
        }
        public static bool IsDefined(this CSharpFieldSymbol symbol, Type type) {
            return symbol.IsDefined(symbol.Compilation.GetType(type));
        }

        public static IEnumerable<CSharpAttributeSymbol> Attributes(this CSharpMethodSymbol symbol, Type type, bool declaredOnly = false) {
            return symbol.Attributes(symbol.Compilation.GetType(type), declaredOnly);
        }
        public static CSharpAttributeSymbol GetAttribute(this CSharpMethodSymbol symbol, Type type, bool declaredOnly = false) {
            return symbol.GetAttributes(symbol.Compilation.GetType(type));
        }
        public static IEnumerable<dynamic> SyntheticAttributes(this CSharpMethodSymbol symbol, Type type = null, bool declaredOnly = false) {
            return symbol.SyntheticAttributes(symbol.Compilation.GetType(type));
        }
        public static dynamic GetSyntheticAttribute(this CSharpMethodSymbol symbol, Type type, bool declaredOnly = false) {
            return symbol.GetSyntheticAttribute(symbol.Compilation.GetType(type));
        }
        public static bool IsDefined(this CSharpMethodSymbol symbol, Type type, bool declaredOnly = false) {
            return symbol.IsDefined(symbol.Compilation.GetType(type));
        }

        public static IEnumerable<CSharpAttributeSymbol> Attributes(this CSharpParameterSymbol symbol, Type type, bool declaredOnly = false) {
            return symbol.Attribute(symbol.Compilation.GetType(type), declaredOnly);
        }
        public static CSharpAttributeSymbol GetAttribute(this CSharpParameterSymbol symbol, Type type, bool declaredOnly = false) {
            return symbol.GetAttribute(symbol.Compilation.GetType(type));
        }
        public static IEnumerable<dynamic> SyntheticAttributes(this CSharpParameterSymbol symbol, Type type = null, bool declaredOnly = false) {
            return symbol.SyntheticAttributes(symbol.Compilation.GetType(type));
        }
        public static dynamic GetSyntheticAttribute(this CSharpParameterSymbol symbol, Type type, bool declaredOnly = false) {
            return symbol.GetSyntheticAttribute(symbol.Compilation.GetType(type));
        }
        public static bool IsDefined(this CSharpParameterSymbol symbol, Type type, bool declaredOnly = false) {
            return symbol.IsDefined(symbol.Compilation.GetType(type));
        }

        public static IEnumerable<CSharpAttributeSymbol> Attributes(this CSharpPropertySymbol symbol, Type type, bool declaredOnly = false) {
            return symbol.Attributes(symbol.Compilation.GetType(type), declaredOnly);
        }
        public static CSharpAttributeSymbol GetAttribute(this CSharpPropertySymbol symbol, Type type, bool declaredOnly = false) {
            return symbol.GetAttributes(symbol.Compilation.GetType(type));
        }
        public static IEnumerable<dynamic> SyntheticAttributes(this CSharpPropertySymbol symbol, Type type = null, bool declaredOnly = false) {
            return symbol.SyntheticAttributes(symbol.Compilation.GetType(type));
        }
        public static dynamic GetSyntheticAttribute(this CSharpPropertySymbol symbol, Type type, bool declaredOnly = false) {
            return symbol.GetSyntheticAttribute(symbol.Compilation.GetType(type));
        }
        public static bool IsDefined(this CSharpPropertySymbol symbol, Type type, bool declaredOnly = false) {
            return symbol.IsDefined(symbol.Compilation.GetType(type));
        }
    }

    public abstract class CSharpSymbol {

        private CSharpCompilation m_compilation;
        private Symbol m_symbol;

        public CSharpSymbol(CSharpCompilation compilation, Symbol symbol) {
            m_compilation = compilation;
            m_symbol = symbol;
        }

        internal IEnumerable<CSharpAttributeSymbol> Attribute(CSharpTypeSymbol type, bool declaredOnly = false) {
            if (type != null && type.IsNamedType)
                return m_symbol.GetAttributes(type.NamedTypeSymbol).Select(o => new CSharpAttributeSymbol(Compilation, this, o));

            return m_symbol.GetAttributes().Select(o => new CSharpAttributeSymbol(Compilation, this, o));
        }
        internal CSharpAttributeSymbol GetAttribute(CSharpTypeSymbol type, bool declaredOnly = false) {
            return Attribute(type, declaredOnly).SingleOrDefault();
        }
        internal IEnumerable<dynamic> SyntheticAttributes(CSharpTypeSymbol type = null, bool declaredOnly = false) {
            return Attribute(type, declaredOnly).Select(o => new CSharpSyntheticAttribute(o));
        }
        internal dynamic GetSyntheticAttribute(CSharpTypeSymbol type, bool declaredOnly = false) {
            return SyntheticAttributes(type, declaredOnly).SingleOrDefault();
        }
        internal bool IsDefined(CSharpTypeSymbol type, bool declaredOnly = false) {
            return Attribute(type, declaredOnly).Any();
        }

        public CSharpCompilation Compilation {
            get { return m_compilation; }
        }
        public Symbol Symbol {
            get { return m_symbol; }
        }
        public abstract CSharpSymbol DeclaringSymbol { get; }

        public override int GetHashCode() {
            return m_symbol.GetHashCode();
        }
        public override bool Equals(object obj) {
            if (obj == null)
                return false;
            return obj.Equals(m_symbol);
        }
    }

    public sealed class CSharpCompilation {

        public static CSharpCompilation Create(params string[] references) {
            return Create(references.Select(o => new CSharpReference(file: o)).ToArray());
        }
        public static CSharpCompilation Create(params CSharpReference[] references) {
            return new CSharpCompilation(references);
        }

        private static bool SymbolEquals(object lhs, object rhs) {
            var lhsPointer = lhs as PointerTypeSymbol;
            var rhsPointer = rhs as PointerTypeSymbol;

            if (lhsPointer != null && rhsPointer != null)
                return lhsPointer.PointedAtType.Equals(rhsPointer.PointedAtType);

            return object.Equals(lhs, rhs);
        }
        private static int SymbolGetHashcode(object o) {
            if (o == null)
                throw new ArgumentNullException();

            return o.GetHashCode();
        }

        private static Dictionary<Symbol, CSharpSymbol> s_symbolToCSharpSymbol;

        // readonly
        private readonly Compilation m_compilation;
        private readonly CSharpReference m_mscorlib;
        private readonly IEnumerable<CSharpReference> m_references;
        private readonly Dictionary<string, CSharpReference> m_pathToReferences;

        // caches
        private Dictionary<MetadataReference, AssemblySymbol> m_referenceToAssembly;
        private Dictionary<AssemblyName, CSharpReference> m_nameToReferences;

        // syntax
        private IEnumerable<CSharpCompilationUnit> m_compilationUnits;

        // predefined types
        private Dictionary<CSharpTypeSymbol, string> m_keywordNames;

        internal CSharpCompilation(Compilation compilation) {
            m_compilation = compilation;
            m_references = compilation.References.WhereIs<AssemblyFileReference>()
                .Select(o => new CSharpReference(file: o.Path)).ToArray();

            // associate references by paths
            m_pathToReferences = m_references.Where(o => o.Path != null).ToDictionary(o => o.Path);

            // bind mscorlib by file name
            m_mscorlib = m_pathToReferences
                .Where(o => Path.GetFileName(o.Key).CaseInsensitveEquals("mscorlib.dll"))
                .Select(o => o.Value).FirstOrDefault();
        }
        private CSharpCompilation(CSharpCompilation compilation, IEnumerable<SyntaxTree> trees) {
            m_compilation = compilation.m_compilation.AddSyntaxTrees(trees);
            ThrowOnCompilationError();

            m_references = compilation.m_references;
            m_pathToReferences = compilation.m_pathToReferences;
            m_mscorlib = compilation.m_mscorlib;
        }
        private CSharpCompilation(IEnumerable<CSharpReference> references) {
            m_references = references;

            // associate references by paths
            m_pathToReferences = m_references.Where(o => o.Path != null).ToDictionary(o => o.Path);

            // bind mscorlib by file name
            m_mscorlib = m_pathToReferences
                .Where(o => Path.GetFileName(o.Key).CaseInsensitveEquals("mscorlib.dll"))
                .Select(o => o.Value).FirstOrDefault();

            // create compilation
            if (references.Any(o => o.Project != null))
                throw new ArgumentException("Project references not supported.");
            m_compilation = Compilation.Create("CompilationContext",
                options: new CompilationOptions(assemblyKind: AssemblyKind.DynamicallyLinkedLibrary),
                references: references.Select(o => o.MetadataReference));
            ThrowOnCompilationError();
        }

        private void ThrowOnCompilationError() {
            if (m_compilation.GetDiagnostics().Any())
                //if (m_compilation.GetDiagnostics().Any(o => o.Info.Severity == DiagnosticSeverity.Error))
                throw new ArgumentException(
                    m_compilation.GetDiagnostics().StringJoin(Environment.NewLine, o => o.ToString())
                );
        }
        private AssemblySymbol Get(MetadataReference reference) {
            if (m_referenceToAssembly == null)
                m_referenceToAssembly = new Dictionary<MetadataReference, AssemblySymbol>();

            var assembly = m_referenceToAssembly.GetValueOrDefault(reference);
            if (assembly == null)
                m_referenceToAssembly[reference] = assembly = Compilation.GetReferencedAssemblySymbol(reference);

            return assembly;
        }

        internal string GetTypeKeyword(CSharpTypeSymbol type) {
            if (m_keywordNames == null) {
                m_keywordNames = new[] {
                    new { Type = typeof(object), Keyword = "object" },
                    new { Type = typeof(void), Keyword = "void" },

                    new { Type = typeof(char), Keyword = "char" },
                    new { Type = typeof(string), Keyword = "string" },

                    new { Type = typeof(sbyte), Keyword = "sbyte" },
                    new { Type = typeof(short), Keyword = "short" },
                    new { Type = typeof(int), Keyword = "int" },
                    new { Type = typeof(long), Keyword = "long" },

                    new { Type = typeof(byte), Keyword = "byte" },
                    new { Type = typeof(ushort), Keyword = "ushort" },
                    new { Type = typeof(uint), Keyword = "uint" },
                    new { Type = typeof(ulong), Keyword = "ulong" },

                    new { Type = typeof(float), Keyword = "float" },
                    new { Type = typeof(double), Keyword = "double" },
                    new { Type = typeof(decimal), Keyword = "decimal" },
                }.ToDictionary(o => this.GetType(o.Type), o => o.Keyword);
            }

            return m_keywordNames.GetValueOrDefault(type);
        }
        internal T Get<T>(Symbol symbol) {
            if (symbol == null)
                return default(T);

            if (s_symbolToCSharpSymbol == null)
                s_symbolToCSharpSymbol = new Dictionary<Symbol, CSharpSymbol>(
                    EqualityComparer.Construct<object>(SymbolEquals, SymbolGetHashcode));

            var compilation = this;
            object result = s_symbolToCSharpSymbol.GetOrCacheValue(symbol,
                s => (CSharpSymbol)typeof(T).Activate(compilation, s));

            return (T)result;
        }
        internal Compilation Compilation {
            get { return m_compilation; }
        }
        internal CSharpSemanticModel GetSemanticModel(SyntaxTree tree) {
            return new CSharpSemanticModel(m_compilation.GetSemanticModel(tree), this);
        }

        public CSharpCompilation Add(CSharpCompilationUnit compilationUnit) {
            var tree = compilationUnit.Tree;
            return new CSharpCompilation(this, new[] { tree });
        }
        public IEnumerable<CSharpCompilationUnit> CompilationUnits() {
            if (m_compilationUnits == null)
                m_compilationUnits = m_compilation.SyntaxTrees
                    .ToList(o => new CSharpCompilationUnit(o, this));

            return m_compilationUnits;
        }

        public CSharpCompilation Parse(IEnumerable<TextReader> streams) {
            var trees = streams.Select(o => SyntaxTree.ParseCompilationUnit(o.ReadToEnd()));
            return new CSharpCompilation(this, trees);
        }
        public CSharpCompilation Parse(TextReader stream) {
            return Parse(new[] { stream });
        }
        public CSharpCompilation Parse(string code) {
            return Parse(new[] { new StringReader(code) });
        }

        public CSharpTypeSymbol TryGetType(TypeName assemblyQualifiedName) {
            if (assemblyQualifiedName == null)
                return null;

            // resolve assembly
            var assemblyName = new AssemblyName(assemblyQualifiedName.AssemblyFullName);
            var assembly = TryGetAssembly(assemblyName);
            if (assembly == null)
                return null;

            // resolve type
            return assembly.TryGetType(assemblyQualifiedName.MakeAssembly(null), this);
        }
        public CSharpTypeSymbol GetType(TypeName assemblyQualifiedName) {
            var result = TryGetType(assemblyQualifiedName);
            if (result == null)
                throw new ArgumentException();
            return result;
        }

        public CSharpTypeSymbol TryGetType(string assemblyQualifiedName) {
            int error;
            var typeName = TypeName.TryParse(assemblyQualifiedName, out error);
            if (typeName == null)
                return null;

            return TryGetType(typeName);
        }
        public CSharpTypeSymbol GetType(string assemblyQualifiedName) {
            var result = TryGetType(assemblyQualifiedName);
            if (result == null)
                throw new ArgumentException();
            return result;
        }

        public CSharpAssemblySymbol TryGetAssembly(AssemblyName assemblyName) {
            if (m_nameToReferences == null)
                m_nameToReferences = m_pathToReferences
                    .ToDictionary(
                        o => AssemblyName.GetAssemblyName(o.Key),
                        o => o.Value,
                        AssemblyNameComparer.Value
                    );

            var reference = m_nameToReferences.GetValueOrDefault(assemblyName);
            if (assemblyName.Name.CaseInsensitveEquals("mscorlib"))
                reference = m_mscorlib;

            if (reference == null)
                return null;

            var assembly = Get(reference.MetadataReference);
            return Get<CSharpAssemblySymbol>(assembly);
        }
        public CSharpAssemblySymbol GetAssembly(AssemblyName assemblyName) {
            var result = TryGetAssembly(assemblyName);
            if (result == null)
                throw new ArgumentException();
            return result;
        }

        public CSharpAssemblySymbol TryGetAssembly(string file) {
            var reference = m_pathToReferences.GetValueOrDefault(file);
            if (reference == null)
                return null;

            var assembly = Get(reference.MetadataReference);
            return Get<CSharpAssemblySymbol>(assembly);
        }
        public CSharpAssemblySymbol GetAssembly(string file) {
            var result = TryGetAssembly(file);
            if (result == null)
                throw new ArgumentException();
            return result;
        }

        public IEnumerable<CSharpAssemblySymbol> Assemblies() {
            var assemblies = m_references.Select(o => Get<CSharpAssemblySymbol>(Get(o.MetadataReference)));
            if (Assembly != null)
                assemblies = assemblies.Concat(Assembly);
            return assemblies;
        }
        public IEnumerable<CSharpTypeSymbol> Types() {
            return Assemblies().SelectMany(o => o.Types());
        }

        public CSharpAssemblySymbol Assembly {
            get { return Get<CSharpAssemblySymbol>(m_compilation.Assembly); }
        }
    }

    public class CSharpReference {

        private AssemblyFileReference m_reference;
        private CSharpProject m_project;

        public CSharpReference(AssemblyFileReference reference) {
            m_reference = reference;
        }
        public CSharpReference(CSharpProject project = null) {
            m_project = project;
        }
        public CSharpReference(string file, string alias = null) {
            m_reference = (AssemblyFileReference)AssemblyFileReference.Create(file, alias);
        }

        internal MetadataReference MetadataReference {
            get { return m_reference; }
        }

        public string Alias {
            get {
                if (m_reference == null)
                    return null;
                return m_reference.Alias; 
            }
        }
        public string Path {
            get {
                if (m_reference == null)
                    return null;
                return m_reference.Path; 
            }
        }
        public CSharpProject Project {
            get { return m_project; }
        }

        public override string ToString() {
            if (m_project != null)
                return m_project.ToString();

            return Path;
        }
        public override int GetHashCode() {
            if (m_reference != null)
                return m_reference.GetHashCode();
            return m_project.GetHashCode();
        }
        public override bool Equals(object obj) {
            if (m_reference != null)
                return m_reference.Equals(obj);
            return m_project.Equals(obj);
        }
    }

    public sealed class CSharpAssemblySymbol : CSharpSymbol {

        private static NamespaceOrTypeSymbol TryGetNamespaceOrType(
            NamespaceOrTypeSymbol symbol, string fullname, int? arity) {

            return TryGetNamespaceOrType(symbol, fullname.StringSplit('.').ToStack(), arity);
        }
        private static NamespaceOrTypeSymbol TryGetNamespaceOrType(
            NamespaceOrTypeSymbol symbol, Stack<string> fullname, int? arity) {

            var name = fullname.Pop();

            // resolve namespaces
            if (fullname.Count > 0) {
                symbol = TryGetNamespaceOrType(symbol, fullname, null);
                if (symbol == null)
                    return null;
            }

            // filter on name
            var symbols = symbol.GetMembers(name);

            // filter on arity
            if (arity != null)
                symbol = symbols.OfType<NamedTypeSymbol>().SingleOrDefault(o => o.Arity == arity);
            else
                symbol = (NamespaceOrTypeSymbol)symbols.SingleOrDefault();

            if (symbol == null)
                return null;

            return symbol;
        }

        internal CSharpAssemblySymbol(CSharpCompilation compilation, AssemblySymbol assemblySymbol)
            : base(compilation, assemblySymbol) {
        }

        internal AssemblySymbol AssemblySymbol {
            get { return (AssemblySymbol)Symbol; }
        }

        public AssemblyName AssemblyName {
            get { return AssemblySymbol.AssemblyName; }
        }
        public string Name {
            get { return AssemblyName.Name; }
        }
        public string FullName {
            get { return AssemblyName.FullName; }
        }

        public CSharpNamespaceSymbol GlobalNamespace {
            get { return Compilation.Get<CSharpNamespaceSymbol>(AssemblySymbol.GlobalNamespace); }
        }
        public CSharpModuleSymbol ManifestModule {
            get { return Modules().First(); }
        }

        public CSharpTypeSymbol TryGetType(TypeName fullName, CSharpCompilation compilation = null) {
            if (fullName == null)
                return null;

            // fully qualified?
            if (fullName.HasAssembly)
                return compilation.TryGetType(fullName);

            // resolve element type
            if (fullName.HasElementType) {
                var elementType = TryGetType(fullName.ElementType, compilation);

                if (fullName.IsPointer)
                    return elementType.MakePointerType();

                if (fullName.IsSzArray)
                    return elementType.MakeArrayType();

                if (fullName.IsArray)
                    return elementType.MakeArrayType(fullName.ArrayRank);

                throw new NotImplementedException();
            }

            int arity;

            // try get named namespace (or global namespace) from assembly
            var typeNames = fullName.ToLinkList(o => o.DeclaringType).ToStack();
            var typeName = typeNames.Pop();
            var ns = TryGetNamespace(typeName.Namespace);
            if (ns == null)
                return null;

            // try get type from namespace
            var name = typeName.GetCSharpName(out arity);
            var type = ns.TryGetType(name, arity);
            if (type == null)
                return null;

            // try get nested types
            while ((typeName = typeNames.PopOrDefault()) != null) {

                // resolve symbol
                name = typeName.GetCSharpName(out arity);
                type = type.GetNestedType(name, arity: arity);
                if (type == null)
                    return null;
            }

            // generic arguments
            if (fullName.HasGenericArguments)
                type = type.MakeGenericType(fullName.GenericArguments().Select(o => GetType(o, compilation)));

            // return result
            return type;
        }
        public CSharpTypeSymbol GetType(TypeName fullName, CSharpCompilation compilation = null) {
            var result = TryGetType(fullName, compilation);
            if (result == null)
                throw new ArgumentException();
            return result;
        }

        public CSharpTypeSymbol TryGetType(string fullname, CSharpCompilation compilation = null) {
            if (string.IsNullOrEmpty(fullname))
                return null;

            int error;
            var typeName = TypeName.TryParse(fullname, out error);
            if (typeName == null)
                return null;

            if (typeName.AssemblyFullName != null &&
                !typeName.AssemblyFullName.CaseInsensitveEquals(FullName))
                return null;

            return TryGetType(typeName, compilation);
        }
        public CSharpTypeSymbol GetType(string fullname, CSharpCompilation compilation = null) {
            var typeSymbol = TryGetType(fullname, compilation);
            if (typeSymbol == null)
                throw new ArgumentException();
            return typeSymbol;
        }

        public CSharpNamespaceSymbol TryGetNamespace(string ns) {
            if (string.IsNullOrEmpty(ns))
                return GlobalNamespace;

            var nsSymbol = TryGetNamespaceOrType(GlobalNamespace.NamespaceSymbol, ns, null) as NamespaceSymbol;

            return Compilation.Get<CSharpNamespaceSymbol>(nsSymbol);
        }
        public CSharpNamespaceSymbol GetNamespace(string ns) {
            var nsSymbol = TryGetNamespace(ns);
            if (nsSymbol == null)
                throw new ArgumentException();
            return nsSymbol;
        }

        public IEnumerable<CSharpNamespaceSymbol> Namespaces() {
            return Node.Create(GlobalNamespace).SelectDescendentsAndSelf(o => o.Namespaces());
        }
        public IEnumerable<CSharpTypeSymbol> Types() {
            return Namespaces().SelectMany(o => o.Types());
        }
        public IEnumerable<CSharpModuleSymbol> Modules() {
            return AssemblySymbol.Modules.Select(o => Compilation.Get<CSharpModuleSymbol>(o));
        }

        #region Attributes
        public IEnumerable<CSharpAttributeSymbol> Attributes(CSharpTypeSymbol type = null) {
            return base.Attribute(type);
        }
        public CSharpAttributeSymbol GetAttribute(CSharpTypeSymbol type = null) {
            return base.GetAttribute(type);
        }
        public IEnumerable<dynamic> SyntheticAttributes(CSharpTypeSymbol type = null) {
            return base.SyntheticAttributes(type);
        }
        public dynamic GetSyntheticAttribute(CSharpTypeSymbol type) {
            return base.GetSyntheticAttribute(type);
        }
        public bool IsDefined(CSharpTypeSymbol type) {
            return base.IsDefined(type);
        }
        #endregion

        public override CSharpSymbol DeclaringSymbol {
            get { return null; }
        }

        public override string ToString() {
            return FullName;
        }
    }

    public sealed class CSharpModuleSymbol : CSharpSymbol {

        internal CSharpModuleSymbol(CSharpCompilation compilation, ModuleSymbol moduleSymbol)
            : base(compilation, moduleSymbol) {
        }

        internal ModuleSymbol ModuleSymbol {
            get { return (ModuleSymbol)Symbol; }
        }

        public CSharpAssemblySymbol Assembly {
            get { return Compilation.Get<CSharpAssemblySymbol>(ModuleSymbol.ContainingAssembly); }
        }
        public override CSharpSymbol DeclaringSymbol {
            get { return Assembly; }
        }

        #region Attributes
        public IEnumerable<CSharpAttributeSymbol> Attributes(CSharpTypeSymbol type = null) {
            return base.Attribute(type);
        }
        public CSharpAttributeSymbol GetAttribute(CSharpTypeSymbol type = null) {
            return base.GetAttribute(type);
        }
        public IEnumerable<dynamic> SyntheticAttributes(CSharpTypeSymbol type = null) {
            return base.SyntheticAttributes(type);
        }
        public dynamic GetSyntheticAttribute(CSharpTypeSymbol type) {
            return base.GetSyntheticAttribute(type);
        }
        public bool IsDefined(CSharpTypeSymbol type) {
            return base.IsDefined(type);
        }
        #endregion

        public override string ToString() {
            return ModuleSymbol.Name;
        }
    }

    public sealed class CSharpNamespaceSymbol : CSharpSymbol {

        internal CSharpNamespaceSymbol(CSharpCompilation compilation, NamespaceSymbol namespaceSymbol)
            : base(compilation, namespaceSymbol) {
        }

        internal NamespaceSymbol NamespaceSymbol {
            get { return (NamespaceSymbol)Symbol; }
        }

        public CSharpNamespaceSymbol DeclaringNamespace {
            get { return Compilation.Get<CSharpNamespaceSymbol>(NamespaceSymbol.ContainingNamespace); }
        }
        public override CSharpSymbol DeclaringSymbol {
            get { return DeclaringNamespace; }
        }
        public string Name {
            get {
                if (string.IsNullOrEmpty(NamespaceSymbol.Name))
                    return null;

                return NamespaceSymbol.Name;
            }
        }
        public string Fullname {
            get {
                var result = Name;

                var declaringNamespace = DeclaringNamespace;
                if (declaringNamespace != null && !declaringNamespace.IsGlobalNamespace)
                    result = declaringNamespace.Fullname + "." + Name;

                return result;
            }
        }

        public bool IsGlobalNamespace {
            get { return NamespaceSymbol.IsGlobalNamespace; }
        }

        public CSharpTypeSymbol TryGetType(string name, int arity = 0) {
            var type = NamespaceSymbol.GetTypeMembers(name, arity).SingleOrDefault();
            return Compilation.Get<CSharpTypeSymbol>(type);
        }
        public CSharpTypeSymbol GetType(string name, int arity = 0) {
            var type = TryGetType(name);
            if (type == null)
                throw new ArgumentException();
            return type;
        }

        public IEnumerable<CSharpNamespaceSymbol> Namespaces() {
            return NamespaceSymbol.GetNamespaceMembers()
                .Select(o => Compilation.Get<CSharpNamespaceSymbol>(o));
        }
        public IEnumerable<CSharpTypeSymbol> Types() {
            return NamespaceSymbol.GetTypeMembers()
                .Select(o => Compilation.Get<CSharpTypeSymbol>(o));
        }

        public override string ToString() {
            return Name;
        }
    }

    public abstract class CSharpMemberSymbol : CSharpSymbol {

        public CSharpMemberSymbol(CSharpCompilation compilation, Symbol memberSymbol)
            : base(compilation, memberSymbol) {
        }

        internal virtual Accessibility Accessibility {
            get { return Symbol.DeclaredAccessibility; }
        }

        public bool IsPublic {
            get { return Accessibility == Accessibility.Public; }
        }
        public bool IsPrivate {
            get { return Accessibility == Accessibility.Private; }
        }
        public bool IsInternal {
            get { return Accessibility == Accessibility.Internal; }
        }
        public bool IsProtected {
            get { return Accessibility == Accessibility.Protected; }
        }
        public bool IsProtectedAndInternal {
            get { return Accessibility == Accessibility.ProtectedAndInternal; }
        }
        public bool IsProtectedInternal {
            get { return Accessibility == Accessibility.ProtectedInternal; }
        }

        public virtual string Name {
            get { return Symbol.Name; }
        }
        public CSharpTypeSymbol DeclaringType {
            get { return Compilation.Get<CSharpTypeSymbol>(Symbol.ContainingType); }
        }
        public override CSharpSymbol DeclaringSymbol {
            get { return DeclaringType; }
        }
        public virtual CSharpNamespaceSymbol DeclaringNamespace {
            get { return Compilation.Get<CSharpNamespaceSymbol>(Symbol.ContainingNamespace); }
        }
        public CSharpAssemblySymbol Assembly {
            get { return Compilation.Get<CSharpAssemblySymbol>(Symbol.ContainingAssembly); }
        }

        #region Attributes
        public IEnumerable<CSharpAttributeSymbol> Attributes(CSharpTypeSymbol type = null, bool declaredOnly = false) {
            return base.Attribute(type, declaredOnly);
        }
        public CSharpAttributeSymbol GetAttributes(CSharpTypeSymbol type = null, bool declaredOnly = false) {
            return base.GetAttribute(type, declaredOnly);
        }
        public new IEnumerable<dynamic> SyntheticAttributes(CSharpTypeSymbol type = null, bool declaredOnly = false) {
            return base.SyntheticAttributes(type, declaredOnly);
        }
        public new dynamic GetSyntheticAttribute(CSharpTypeSymbol type, bool declaredOnly = false) {
            return base.GetSyntheticAttribute(type, declaredOnly);
        }
        public new bool IsDefined(CSharpTypeSymbol type, bool declaredOnly = false) {
            return base.IsDefined(type, declaredOnly);
        }
        #endregion

        public override string ToString() {
            return DeclaringType.ToString() + "." + Symbol.Name;
        }
    }

    public sealed class CSharpTypeSymbol : CSharpMemberSymbol {

        public static bool operator ==(CSharpTypeSymbol lhs, Type rhs) {
            if (rhs == null || lhs == null)
                return object.Equals(lhs, rhs);

            var compilation = lhs.Compilation;
            if (compilation == null)
                throw new InvalidOperationException();
            return lhs == compilation.GetType(rhs);
        }
        public static bool operator ==(Type lhs, CSharpTypeSymbol rhs) {
            return rhs == lhs;
        }
        public static bool operator !=(CSharpTypeSymbol lhs, Type rhs) {
            return !(lhs == rhs);
        }
        public static bool operator !=(Type lhs, CSharpTypeSymbol rhs) {
            return !(lhs == rhs);
        }

        private Dictionary<Symbol, CSharpMemberSymbol> m_members;

        public CSharpTypeSymbol(CSharpCompilation compilation, TypeSymbol typeSymbol)
            : base(compilation, typeSymbol) {
        }

        #region Private Members
        private Type GetSymbolType(Type symbolType) {

            if (typeof(FieldSymbol).IsAssignableFrom(symbolType))
                return typeof(CSharpFieldSymbol);

            if (typeof(MethodSymbol).IsAssignableFrom(symbolType))
                return typeof(CSharpMethodSymbol);

            if (typeof(PropertySymbol).IsAssignableFrom(symbolType))
                return typeof(CSharpPropertySymbol);

            if (typeof(TypeSymbol).IsAssignableFrom(symbolType))
                return typeof(CSharpTypeSymbol);

            throw new NotSupportedException();
        }
        private TypeName GetTypeName(bool strongNamed = false, bool genericArguments = false) {

            TypeName typeName = null;

            if (ElementType != null) {
                // element type; add mangling after elementType's name
                var elementType = ElementType.GetTypeName(strongNamed, genericArguments);

                // pointer type
                if (IsPointer)
                    typeName = elementType.MakePointer();

                // array type
                else if (ArrayRank == 1)
                    typeName = elementType.MakeArray();

                // szArray type
                else
                    typeName = elementType.MakeArray(ArrayRank);

            } else {
                var metadataName = MetadataName;

                if (DeclaringType == null) {
                    // root type; add namespace and assembly
                    var assemblyName = strongNamed ? Assembly.FullName : null;
                    typeName = TypeName.Create(metadataName, Namespace, assemblyName);

                } else {
                    // nested type; add only nested type name
                    var declaringType = DeclaringType.GetTypeName(strongNamed);
                    typeName = declaringType.MakeNestedType(metadataName);
                }

                // generic type; add generic arguments if asked to provide them
                if (genericArguments && IsGenericType && !ContainsGenericParameters)
                    typeName = typeName.MakeGenericType(GenericArguments().Select(o => o.GetTypeName(true, true)));
            }


            return typeName;
        }
        private string ElementNameDecoration {
            get {
                if (!HasElementType)
                    return null;

                var result = default(string);

                if (IsPointer)
                    return result + "*";

                if (IsArray)
                    return result + "[" + "".PadLeft(ArrayRank - 1, ',') + "]";

                return result;
            }
        }

        private T GetMember<T>(
            string name = null, object types = null, int arity = 0,
            bool? isPublic = null, bool? isStatic = null,
            bool declaredOnly = false, bool useExactBinding = false) where T : CSharpMemberSymbol {

            return Members<T>(name, types, arity,
                isPublic, isStatic,
                declaredOnly, useExactBinding).SingleOrDefault();
        }

        private IEnumerable<T> Members<T>(
            string name = null, object types = null, int arity = 0,
            bool? isPublic = null, bool? isStatic = null,
            bool declaredOnly = false, bool useExactBinding = false) where T : CSharpMemberSymbol {

            if (IsGenericParameter)
                return Enumerable.Empty<T>();

            return Members(name, types, arity,
                isPublic, isStatic,
                declaredOnly, useExactBinding).OfType<T>();
        }
        #endregion

        #region Internal Members
        internal CSharpMemberSymbol Get(Symbol symbol) {
            if (symbol == null)
                return null;

            if (m_members == null)
                m_members = new Dictionary<Symbol, CSharpMemberSymbol>();

            Type type = GetSymbolType(symbol.GetType());

            if (type == typeof(CSharpTypeSymbol))
                return Compilation.Get<CSharpTypeSymbol>(symbol);

            var result = m_members.GetOrCacheValue(symbol,
                s => (CSharpMemberSymbol)type.Activate(Compilation, s));

            return result;
        }

        internal override Accessibility Accessibility {
            get {
                if (IsArray)
                    return Accessibility.Public;

                if (IsGenericParameter) {
                    if (DeclaringMethod != null)
                        return DeclaringMethod.Accessibility;

                    return DeclaringType.Accessibility;
                }

                return Symbol.DeclaredAccessibility;
            }
        }

        internal TypeSymbol TypeSymbol {
            get { return (TypeSymbol)Symbol; }
        }
        internal TypeParameterSymbol TypeParameterSymbol {
            get { return (TypeParameterSymbol)Symbol; }
        }
        internal NamedTypeSymbol NamedTypeSymbol {
            get { return (NamedTypeSymbol)Symbol; }
        }
        internal PointerTypeSymbol PointerTypeSymbol {
            get { return (PointerTypeSymbol)Symbol; }
        }
        internal ArrayTypeSymbol ArrayTypeSymbol {
            get { return (ArrayTypeSymbol)Symbol; }
        }

        internal bool TypeCheckEnum(dynamic value) {

            var type = ((object)value).GetType();
            if (type.IsPrimitive)
                return true;

            if (!type.IsEnum)
                return false;

            return Compilation.TryGetType(type) == this;
        }
        #endregion

        #region Identity
        public override CSharpSymbol DeclaringSymbol {
            get { return (CSharpSymbol)DeclaringMethod ?? DeclaringType; }
        }
        public CSharpMethodSymbol DeclaringMethod {
            get { return Compilation.Get<CSharpMethodSymbol>(TypeSymbol.ContainingSymbol as MethodSymbol); }
        }
        public CSharpTypeSymbol BaseType {
            get {
                if (IsGenericParameter) {
                    if (HasStructConstraint)
                        return Compilation.GetType(typeof(ValueType));

                    var classConstraint = GenericParameterConstraints().FirstOrDefault(o => o.IsClass);
                    if (classConstraint != null)
                        return classConstraint;

                    return Compilation.GetType(typeof(object));
                }

                return Compilation.Get<CSharpTypeSymbol>(TypeSymbol.BaseType);
            }
        }
        public IEnumerable<CSharpTypeSymbol> Interfaces() {
            return TypeSymbol.AllInterfaces.Select(o => Compilation.Get<CSharpTypeSymbol>(o));
        }
        public Type RuntimeType {
            get { throw new NotImplementedException(); }
        }
        #endregion

        #region ElementType
        public bool HasElementType {
            get { return ElementType != null; }
        }
        public CSharpTypeSymbol ElementType {
            get {
                if (IsArray)
                    return Compilation.Get<CSharpTypeSymbol>(ArrayTypeSymbol.ElementType);

                if (IsPointer)
                    return Compilation.Get<CSharpTypeSymbol>(PointerTypeSymbol.PointedAtType);

                return null;
            }
        }
        public CSharpTypeSymbol RootElementType {
            get {
                if (!HasElementType)
                    return this;

                var elementType = this;
                while (elementType.HasElementType)
                    elementType = elementType.ElementType;

                return elementType;
            }
        }
        public CSharpTypeSymbol MakePointerType() {
            return Compilation.Get<CSharpTypeSymbol>(Compilation.Compilation.CreatePointerTypeSymbol(TypeSymbol));
        }
        public CSharpTypeSymbol MakeArrayType(int rank = 1) {
            return Compilation.Get<CSharpTypeSymbol>(Compilation.Compilation.CreateArrayTypeSymbol(TypeSymbol, rank));
        }
        public int ArrayRank {
            get {
                if (!IsArray)
                    throw new InvalidOperationException();

                return ArrayTypeSymbol.Rank;
            }
        }
        #endregion

        #region MetadataName
        public string MetadataName {
            get {
                if (HasElementType)
                    return ElementType.MetadataName + ElementNameDecoration;

                var name = Name;

                if (IsGenericType)
                    name += "`" + GenericArity;

                return name;
            }
        }
        public string MetadataFullName {
            get {
                if (ContainsGenericParameters && !IsGenericTypeDefinition)
                    return null;

                if (IsGenericParameter)
                    return null;

                return GetTypeName(genericArguments: true).ToString();
            }
        }
        public string AssemblyQualifiedName {
            get {
                if (ContainsGenericParameters && !IsGenericTypeDefinition)
                    return null;

                if (IsGenericParameter)
                    return null;

                return GetTypeName(strongNamed: true, genericArguments: true).ToString();
            }
        }
        #endregion

        #region Name
        public string KeywordName {
            get { return Compilation.GetTypeKeyword(this); }
        }
        public override string Name {
            get {
                if (HasElementType)
                    return ElementType.Name + ElementNameDecoration;

                return Symbol.Name;
            }
        }
        public string Namespace {
            get {
                if (HasElementType)
                    return RootElementType.Namespace;

                return DeclaringNamespace != null ? DeclaringNamespace.Fullname : null;
            }
        }
        #endregion

        #region Modifiers
        public bool IsAbstract {
            get { return TypeSymbol.IsAbstract; }
        }
        public bool IsSealed {
            get {
                if (IsArray)
                    return true;

                return TypeSymbol.IsSealed;
            }
        }
        public bool IsExtern {
            get { return TypeSymbol.IsExtern; }
        }
        public bool IsStatic {
            get { return TypeSymbol.IsStatic; }
        }
        #endregion

        #region Type of Type
        public bool IsValueType {
            get { return TypeSymbol.IsValueType; }
        }
        public bool IsReferenceType {
            get {
                if (IsGenericParameter && GenericParameterConstraints().Any(o => o.IsClass))
                    return true;

                return TypeSymbol.IsReferenceType;
            }
        }

        public bool IsInterface {
            get { return TypeSymbol.TypeKind == TypeKind.Interface; }
        }
        public bool IsClass {
            get { return TypeSymbol.TypeKind == TypeKind.Class; }
        }
        public bool IsStruct {
            get { return TypeSymbol.TypeKind == TypeKind.Struct; }
        }
        public bool IsEnum {
            get { return TypeSymbol.TypeKind == TypeKind.Enum; }
        }
        public bool IsArray {
            get { return TypeSymbol.TypeKind == TypeKind.ArrayType; }
        }
        public bool IsDelegate {
            get { return TypeSymbol.TypeKind == TypeKind.Delegate; }
        }
        public bool IsPointer {
            get { return TypeSymbol.TypeKind == TypeKind.PointerType; }
        }
        #endregion

        #region Generic Type
        public bool IsNamedType {
            get { return Symbol is NamedTypeSymbol; }
        }

        public int GenericArity {
            get {
                if (!IsGenericType)
                    return 0;

                return NamedTypeSymbol.Arity;
            }
        }
        public bool IsGenericType {
            get { return GenericArguments().Any(); }
        }
        public bool IsGenericTypeDefinition {
            get { return IsGenericType && this == GenericTypeDefinition; }
        }
        public bool ContainsGenericParameters {
            get {
                if (IsGenericTypeDefinition)
                    return true;

                return GenericArguments().Any(o => o.IsGenericParameter || o.ContainsGenericParameters);
            }
        }
        public CSharpTypeSymbol GenericTypeDefinition {
            get {
                if (!IsGenericType)
                    return this;
                return Compilation.Get<CSharpTypeSymbol>(Symbol.OriginalDefinition);
            }
        }
        public IEnumerable<CSharpTypeSymbol> GenericArguments() {
            if (!IsNamedType)
                return Enumerable.Empty<CSharpTypeSymbol>();
            return NamedTypeSymbol.TypeArguments.Select(o => Compilation.Get<CSharpTypeSymbol>(o));
        }
        public CSharpTypeSymbol MakeGenericType(IEnumerable<CSharpTypeSymbol> genericArguments) {
            return MakeGenericType(genericArguments.ToArray());
        }
        public CSharpTypeSymbol MakeGenericType(params CSharpTypeSymbol[] genericArguments) {
            if (!IsGenericTypeDefinition)
                throw new InvalidOperationException();

            var arguments = genericArguments.Select(o => o.TypeSymbol);

            return Compilation.Get<CSharpTypeSymbol>(NamedTypeSymbol.Construct(arguments.ToArray()));
        }
        #endregion

        #region Generic Parameter
        public bool IsGenericParameter {
            get { return Symbol is TypeParameterSymbol; }
        }
        public int GenericParameterPosition {
            get {
                if (!IsGenericParameter)
                    throw new InvalidOperationException();

                return TypeParameterSymbol.DeclaringType.TypeParameters.IndexOf(TypeParameterSymbol);
            }
        }
        public IEnumerable<CSharpTypeSymbol> GenericParameterConstraints() {
            if (!IsGenericParameter)
                return Enumerable.Empty<CSharpTypeSymbol>();

            return TypeParameterSymbol.ConstraintTypes.Select(o => Compilation.Get<CSharpTypeSymbol>(o));
        }
        public bool HasClassConstraint {
            get {
                if (!IsGenericParameter)
                    return false;

                return TypeParameterSymbol.HasReferenceTypeConstraint;
            }
        }
        public bool HasStructConstraint {
            get {
                if (!IsGenericParameter)
                    return false;

                return TypeParameterSymbol.HasValueTypeConstraint;
            }
        }
        public bool HasNewConstraint {
            get {
                if (!IsGenericParameter)
                    return false;

                return TypeParameterSymbol.HasConstructorConstraint;
            }
        }
        public bool HasOutConstraint {
            get {
                if (!IsGenericParameter)
                    return false;

                return TypeParameterSymbol.Variance == VarianceKind.Out;
            }
        }
        public bool HasInConstraint {
            get {
                if (!IsGenericParameter)
                    return false;

                return TypeParameterSymbol.Variance == VarianceKind.In;
            }
        }
        #endregion

        #region Members
        public CSharpFieldSymbol GetField(string name = null, bool? isPublic = null, bool? isStatic = null, bool declaredOnly = false) {
            return GetMember<CSharpFieldSymbol>(name, isPublic: isPublic, isStatic: isStatic, declaredOnly: declaredOnly);
        }
        public IEnumerable<CSharpFieldSymbol> Fields(string name = null, bool? isPublic = null, bool? isStatic = null, bool declaredOnly = false) {
            return Members<CSharpFieldSymbol>(name, isPublic: isPublic, isStatic: isStatic, declaredOnly: declaredOnly);
        }
        public CSharpPropertySymbol GetProperty(string name = null, bool? isPublic = null, bool? isStatic = null, bool declaredOnly = false) {
            return GetMember<CSharpPropertySymbol>(name, isPublic: isPublic, isStatic: isStatic, declaredOnly: declaredOnly);
        }
        public IEnumerable<CSharpPropertySymbol> Properties(string name = null, bool? isPublic = null, bool? isStatic = null, bool declaredOnly = false) {
            return Members<CSharpPropertySymbol>(name, isPublic: isPublic, isStatic: isStatic, declaredOnly: declaredOnly);
        }
        public CSharpTypeSymbol GetNestedType(string name = null, int arity = 0, bool? isPublic = null, bool? isStatic = null, bool declaredOnly = false) {
            return GetMember<CSharpTypeSymbol>(name, arity: arity, isPublic: isPublic, isStatic: isStatic, declaredOnly: declaredOnly);
        }
        public IEnumerable<CSharpTypeSymbol> NestedTypes(string name = null, int arity = 0, bool? isPublic = null, bool? isStatic = null, bool declaredOnly = false) {
            return Members<CSharpTypeSymbol>(name, arity: arity, isPublic: isPublic, isStatic: isStatic, declaredOnly: declaredOnly);
        }

        public CSharpMethodSymbol GetMethod(
            string name = null, object types = null,
            bool? isPublic = null, bool? isStatic = null,
            bool declaredOnly = false, bool useExactBinding = false) {

            return GetMember<CSharpMethodSymbol>(name, types: types,
                isPublic: isPublic, isStatic: isStatic,
                declaredOnly: declaredOnly, useExactBinding: useExactBinding);
        }
        public IEnumerable<CSharpMethodSymbol> Methods(
            string name = null, object types = null,
            bool? isPublic = null, bool? isStatic = null,
            bool declaredOnly = false, bool useExactBinding = false) {

            return Members<CSharpMethodSymbol>(name, types: types,
                isPublic: isPublic, isStatic: isStatic,
                declaredOnly: declaredOnly, useExactBinding: useExactBinding);
        }

        public CSharpMemberSymbol GetMember(
            string name = null, object types = null, int arity = 0,
            bool? isPublic = null, bool? isStatic = null,
            bool declaredOnly = false, bool useExactBinding = false) {

            return Members(name, types, arity,
                isPublic, isStatic,
                declaredOnly, useExactBinding).Single();
        }

        public IEnumerable<CSharpMemberSymbol> Members(
            string name = null, object types = null, int arity = 0,
            bool? isPublic = null, bool? isStatic = null,
            bool declaredOnly = false, bool useExactBinding = false) {

            if (IsGenericParameter)
                return Enumerable.Empty<CSharpMemberSymbol>();

            var members =
                from o in name == null ? NamedTypeSymbol.GetMembers() : NamedTypeSymbol.GetMembers(name)
                where name == null || o.Name == name
                let type = o as NamedTypeSymbol where type == null || type.Arity == arity
                let method = o as MethodSymbol where method == null || method.Arity == arity
                where isStatic == null || isStatic == o.IsStatic
                where isPublic == null || (o.DeclaredAccessibility == Accessibility.Public) == isPublic
                select o;

            return members.Select(o => Get(o));
        }
        #endregion

        #region Enum
        private bool? m_enumContainsFlagsCache;

        public bool EnumContainsFlags {
            get {
                if (!IsEnum)
                    return false;

                if (m_enumContainsFlagsCache == null)
                    m_enumContainsFlagsCache = IsDefined(Compilation.GetType(typeof(FlagsAttribute)));

                return (bool)m_enumContainsFlagsCache;
            }
        }
        public IEnumerable<CSharpFieldSymbol> EnumFields() {
            if (!IsEnum)
                return Enumerable.Empty<CSharpFieldSymbol>();

            var fields =
                from o in Fields()
                orderby o.IsEnumUberFlag, (long)o.RawConstantValue
                select o;

            return fields;
        }
        public IEnumerable<CSharpFieldSymbol> EnumValues(dynamic value = null, bool minimumSet = true) {
            if (value == null)
                minimumSet = false;

            if (!IsEnum)
                return Enumerable.Empty<CSharpFieldSymbol>();

            var result = EnumFields().Where(o => !o.IsEnumMask);

            if (value != null)
                result = result.Where(o => o.TestEnumValue(value));

            if (minimumSet) {
                var set = result.ToArray();
                result = result.Where(o => set.All(x => o == x || !o.TestEnumValue(x)));
            }

            return result;
        }
        public IEnumerable<CSharpFieldSymbol> EnumMasks() {
            if (!IsEnum)
                return Enumerable.Empty<CSharpFieldSymbol>();

            return EnumFields().Where(o => o.IsEnumMask);
        }

        public CSharpFieldSymbol GetEnumValue(dynamic value) {
            if (!TypeCheckEnum(value))
                return null;

            var lValue = (long)value;
            return EnumValues().SingleOrDefault(o => (long)o.RawConstantValue == lValue);
        }
        public CSharpFieldSymbol GetEnumMask(dynamic value) {
            if (!TypeCheckEnum(value))
                return null;

            var lValue = (long)value;
            return EnumMasks().SingleOrDefault(o => (long)o.RawConstantValue == lValue);
        }
        #endregion

        public override string ToString() {
            var result = Name;

            if (IsGenericParameter)
                return result;

            if (DeclaringType != null)
                result = DeclaringType.ToString() + "." + Name;

            else if (Namespace != null)
                result = Namespace + "." + result;

            if (IsGenericType)
                result = result + GenericArguments().StringJoin("<", ",", o => o.ToString(), ">");

            return result;
        }
    }

    public sealed class CSharpMethodSymbol : CSharpMemberSymbol {

        private Dictionary<Symbol, CSharpParameterSymbol> m_parameters;

        internal CSharpMethodSymbol(CSharpCompilation compilation, MethodSymbol methodSymbol)
            : base(compilation, methodSymbol) {
        }

        private CSharpParameterSymbol Get(Symbol symbol) {
            if (symbol == null)
                return null;

            if (m_parameters == null)
                m_parameters = new Dictionary<Symbol, CSharpParameterSymbol>();

            var result = m_parameters.GetOrCacheValue(symbol,
                s => (CSharpParameterSymbol)typeof(CSharpParameterSymbol).Activate(Compilation, s));

            return result;
        }
        private MethodSymbol MethodSymbol {
            get { return (MethodSymbol)Symbol; }
        }

        #region Kind
        public bool IsConstructor {
            get { return MethodSymbol.MethodKind == MethodKind.Constructor || IsStaticConstructor; }
        }
        public bool IsInstanceConstructor {
            get { return IsConstructor && !IsStatic; }
        }
        public bool IsStaticConstructor {
            get { return MethodSymbol.MethodKind == MethodKind.StaticConstructor; }
        }
        public bool IsFinalizer {
            get { return MethodSymbol.MethodKind == MethodKind.Destructor; }
        }

        public bool IsPropertyGet {
            get { return MethodSymbol.MethodKind == MethodKind.PropertyGet; }
        }
        public bool IsPropertySet {
            get { return MethodSymbol.MethodKind == MethodKind.PropertySet; }
        }
        public bool IsEventAdd {
            get { return MethodSymbol.MethodKind == MethodKind.EventAdd; }
        }
        public bool IsEventRemove {
            get { return MethodSymbol.MethodKind == MethodKind.EventRemove; }
        }

        public bool IsAnonymousFunction {
            get { return MethodSymbol.MethodKind == MethodKind.AnonymousFunction; }
        }
        public bool IsExplicitInterfaceImplementation {
            get { return MethodSymbol.MethodKind == MethodKind.ExplicitInterfaceImplementation; }
        }
        public bool IsDelegateInvoke {
            get { return MethodSymbol.MethodKind == MethodKind.DelegateInvoke; }
        }

        public bool IsOperator {
            get { return MethodSymbol.MethodKind == MethodKind.Operator; }
        }
        public bool IsConversion {
            get { return MethodSymbol.MethodKind == MethodKind.Conversion; }
        }
        #endregion

        #region Modifiers
        public bool IsVirtual {
            get { return MethodSymbol.IsVirtual; }
        }
        public bool IsAbstract {
            get { return MethodSymbol.IsAbstract; }
        }
        public bool IsOverride {
            get { return MethodSymbol.IsOverride; }
        }
        public bool IsSealed {
            get { return MethodSymbol.IsSealed; }
        }
        public bool IsExtern {
            get { return MethodSymbol.IsExtern; }
        }
        public bool IsStatic {
            get { return MethodSymbol.IsStatic; }
        }
        #endregion

        public IEnumerable<CSharpParameterSymbol> Parameters() {
            return MethodSymbol.Parameters.Select(o => Get(o));
        }

        #region Generic
        public bool IsGenericMethod {
            get { return MethodSymbol.IsGenericMethod; }
        }
        public bool IsGenericMethodDefinition {
            get {
                return MethodSymbol.TypeParameters.ToEnumerable().Zip(
                    MethodSymbol.TypeArguments.ToEnumerable(), (l, r) => l.Equals(r))
                    .All(o => true);
            }
        }
        public bool HasGenericArguments {
            get { return GenericArguments().Any(); }
        }
        public CSharpMethodSymbol GenericMethodDefinition {
            get { return (CSharpMethodSymbol)DeclaringType.Get(MethodSymbol.ConstructedFrom); }
        }
        public IEnumerable<CSharpTypeSymbol> GenericArguments() {
            return MethodSymbol.TypeArguments.Select(o => Compilation.Get<CSharpTypeSymbol>(o));
        }
        public CSharpMethodSymbol MakeGenericMethod(IEnumerable<CSharpTypeSymbol> genericArguments) {
            return MakeGenericMethod(genericArguments.ToArray());
        }
        public CSharpMethodSymbol MakeGenericMethod(params CSharpTypeSymbol[] genericArguments) {
            var arguments = genericArguments.Select(o => o.TypeSymbol);
            var readOnlyArguments = ReadOnlyArray<TypeSymbol>.CreateFrom(arguments.ToArray());
            return Compilation.Get<CSharpMethodSymbol>(MethodSymbol.Construct(readOnlyArguments));
        }
        #endregion
    }

    public sealed class CSharpParameterSymbol : CSharpSymbol {

        internal CSharpParameterSymbol(CSharpCompilation compilation, ParameterSymbol parameterSymbol)
            : base(compilation, parameterSymbol) {
        }

        private ParameterSymbol ParameterSymbol {
            get { return (ParameterSymbol)Symbol; }
        }

        public string Name {
            get { return ParameterSymbol.Name; }
        }
        public int Position {
            get { return ParameterSymbol.Ordinal; }
        }
        public CSharpTypeSymbol Type {
            get { return Compilation.Get<CSharpTypeSymbol>(ParameterSymbol.Type); }
        }

        public CSharpTypeSymbol DeclaringType {
            get { return Compilation.Get<CSharpTypeSymbol>(ParameterSymbol.ContainingType); }
        }
        public CSharpMethodSymbol DeclaringMethod {
            get { return DeclaringType.Get(ParameterSymbol.ContainingSymbol) as CSharpMethodSymbol; }
        }
        public CSharpPropertySymbol DeclaringProperty {
            get { return DeclaringType.Get(ParameterSymbol.ContainingSymbol) as CSharpPropertySymbol; }
        }
        public override CSharpSymbol DeclaringSymbol {
            get { return (CSharpSymbol)DeclaringProperty ?? (CSharpSymbol)DeclaringMethod ?? DeclaringType; }
        }

        public dynamic DefaultValue {
            get {
                var result = ParameterSymbol.DefaultValue;
                if (result == null)
                    return result;

                if (Type.IsEnum)
                    result = CSharpSyntheticEnum.Construct(result, Type);

                return result;
            }
        }
        public bool HasDefaultValue {
            get { return ParameterSymbol.HasDefaultValue; }
        }

        public bool IsExtern {
            get { return ParameterSymbol.IsExtern; }
        }
        public bool IsOptional {
            get { return ParameterSymbol.IsOptional; }
        }
        public bool IsParams {
            get { return ParameterSymbol.IsParams; }
        }
        public bool IsThis {
            get { return ParameterSymbol.IsThis; }
        }
        public bool IsOut {
            get { return ParameterSymbol.RefKind == RefKind.Out; }
        }
        public bool IsRef {
            get { return ParameterSymbol.RefKind == RefKind.Ref; }
        }

        public override string ToString() {
            return ParameterSymbol.Name;
        }
    }

    public sealed class CSharpFieldSymbol : CSharpMemberSymbol {

        internal CSharpFieldSymbol(CSharpCompilation compilation, FieldSymbol fieldSymbol)
            : base(compilation, fieldSymbol) {
        }

        private FieldSymbol FieldSymbol {
            get { return (FieldSymbol)Symbol; }
        }

        internal dynamic RawConstantValue {
            get { return FieldSymbol.ConstantValue; }
        }

        public CSharpTypeSymbol Type {
            get { return Compilation.Get<CSharpTypeSymbol>(FieldSymbol.Type); }
        }

        public dynamic ConstantValue {
            get {
                var result = RawConstantValue;
                if (result == null)
                    return result;

                if (Type.IsEnum)
                    result = CSharpSyntheticEnum.Construct(result, Type);

                return result;
            }
        }
        public bool HasConstantValue {
            get { return FieldSymbol.HasConstantValue; }
        }
        public bool IsConst {
            get { return FieldSymbol.IsConst; }
        }
        public bool IsExtern {
            get { return FieldSymbol.IsExtern; }
        }
        public bool IsReadOnly {
            get { return FieldSymbol.IsReadOnly; }
        }
        public bool IsVolatile {
            get { return FieldSymbol.IsVolatile; }
        }

        #region Enum
        private CSharpFieldSymbol m_enumMask;
        private bool m_isEnumMaskCached;

        public bool IsEnumMask {
            get {
                if (!Type.EnumContainsFlags)
                    return false;

                return Name.EndsWith("Mask");
            }
        }
        public bool IsEnumFlag {
            get { return Type.EnumContainsFlags && !IsEnumMask && !HasEnumMask && !IsEnumDefaultValue; }
        }
        public bool IsEnumUberFlag {
            get { return Type.EnumContainsFlags && RawConstantValue == -1; }
        }
        public bool IsEnumDefaultValue {
            get { return Type.IsEnum && RawConstantValue == 0; }
        }

        public bool HasEnumMask {
            get { return EnumMask != null; }
        }
        public CSharpFieldSymbol EnumMask {
            get {
                if (!Type.EnumContainsFlags || IsEnumMask || IsEnumDefaultValue)
                    return null;

                if (!m_isEnumMaskCached) {
                    m_enumMask = Type.EnumMasks().SingleOrDefault(mask =>
                        ((long)RawConstantValue & (long)mask.RawConstantValue) == (long)RawConstantValue);
                    m_isEnumMaskCached = true;
                }

                return m_enumMask;
            }
        }
        public IEnumerable<CSharpFieldSymbol> EnumMaskValues() {
            if (!IsEnumMask)
                return Enumerable.Empty<CSharpFieldSymbol>();

            return Type.EnumValues().Where(value => !value.IsEnumDefaultValue &&
                ((long)RawConstantValue & (long)value.RawConstantValue) == (long)value.RawConstantValue);
        }
        public IEnumerable<CSharpFieldSymbol> AggregatingEnumFlags() {
            if (!IsEnumFlag)
                return Enumerable.Empty<CSharpFieldSymbol>();

            return Type.EnumValues().Where(o => o.AggregatedEnumFlags().Any(x => x == this));
        }
        public IEnumerable<CSharpFieldSymbol> AggregatedEnumFlags() {
            if (!IsEnumFlag)
                return Enumerable.Empty<CSharpFieldSymbol>();

            return Type.EnumValues().Except(new[] { this })
                .Where(o => !o.IsEnumDefaultValue && o.IsEnumFlag)
                .Where(o => ((long)RawConstantValue & (long)o.RawConstantValue) == (long)o.RawConstantValue);
        }

        public bool TestEnumValue(dynamic value) {

            // allow CSharpFieldSymbol to be passed in as a value
            var asField = value as CSharpFieldSymbol;
            if (asField != null) {
                if (asField.Type != Type)
                    return false;
                value = asField.RawConstantValue;
            }

            // ensure the field is an enum value
            if (!Type.IsEnum)
                throw new InvalidOperationException();

            if (value == null)
                throw new ArgumentException();

            // type check the argument
            if (!Type.TypeCheckEnum(value))
                return false;

            // convert the constant value and the argument to longs
            var lConstant = (long)RawConstantValue;
            var lValue = (long)value;

            // test if both are zero
            if (lValue == 0 || lConstant == 0)
                return lValue == lConstant;

            // mask the value
            if (HasEnumMask || IsEnumMask) {
                var mask = EnumMask ?? this;
                lValue &= (long)mask.RawConstantValue;
            }

            // do any masked enum values match the masked value
            if (IsEnumMask)
                return EnumMaskValues().Any(o => (long)o.RawConstantValue == lValue);

            // this can be flag without a mask if the enum has FlagsAttribute set
            else if (IsEnumFlag)
                return (lValue & lConstant) == lConstant;

            // check the value
            return lValue == lConstant;
        }
        #endregion

        #region Attributes
        public IEnumerable<CSharpAttributeSymbol> Attributes(CSharpTypeSymbol type = null) {
            return base.Attributes(type);
        }
        public CSharpAttributeSymbol GetAttribute(CSharpTypeSymbol type = null) {
            return base.GetAttributes(type);
        }
        public IEnumerable<dynamic> SyntheticAttributes(CSharpTypeSymbol type = null) {
            return base.SyntheticAttributes(type);
        }
        public dynamic GetSyntheticAttribute(CSharpTypeSymbol type) {
            return base.GetSyntheticAttribute(type);
        }
        public bool IsDefined(CSharpTypeSymbol type) {
            return base.IsDefined(type);
        }
        #endregion
    }

    public sealed class CSharpPropertySymbol : CSharpMemberSymbol {

        private Dictionary<Symbol, CSharpParameterSymbol> m_parameters;

        internal CSharpPropertySymbol(CSharpCompilation compilation, PropertySymbol propertySymbol)
            : base(compilation, propertySymbol) {
        }

        private CSharpParameterSymbol Get(Symbol symbol) {
            if (symbol == null)
                return null;

            if (m_parameters == null)
                m_parameters = new Dictionary<Symbol, CSharpParameterSymbol>();

            var result = m_parameters.GetOrCacheValue(symbol,
                s => (CSharpParameterSymbol)typeof(CSharpParameterSymbol).Activate(Compilation, s));

            return result;
        }
        private PropertySymbol PropertySymbol {
            get { return (PropertySymbol)Symbol; }
        }

        public CSharpTypeSymbol Type {
            get { return Compilation.Get<CSharpTypeSymbol>(PropertySymbol.Type); }
        }

        public bool IsAbstract {
            get { return PropertySymbol.IsAbstract; }
        }
        public bool IsExtern {
            get { return PropertySymbol.IsExtern; }
        }
        public bool IsOverride {
            get { return PropertySymbol.IsOverride; }
        }
        public bool IsSealed {
            get { return PropertySymbol.IsSealed; }
        }
        public bool IsStatic {
            get { return PropertySymbol.IsStatic; }
        }
        public bool IsVirtual {
            get { return PropertySymbol.IsVirtual; }
        }

        public CSharpPropertySymbol OverriddenProperty {
            get { return Compilation.Get<CSharpPropertySymbol>(PropertySymbol.OverriddenProperty); }
        }

        public bool IsReadOnly {
            get { return PropertySymbol.IsReadOnly; }
        }
        public bool IsWriteOnly {
            get { return PropertySymbol.IsWriteOnly; }
        }

        public CSharpMethodSymbol GetMethod {
            get { return (CSharpMethodSymbol)DeclaringType.Get(PropertySymbol.GetMethod); }
        }
        public CSharpMethodSymbol SetMethod {
            get { return (CSharpMethodSymbol)DeclaringType.Get(PropertySymbol.SetMethod); }
        }

        public bool IsIndexer {
            get { return PropertySymbol.IsIndexer; }
        }
        public IEnumerable<CSharpParameterSymbol> Parameters {
            get { return PropertySymbol.Parameters.Select(o => Get(o)); }
        }
    }

    public sealed class CSharpAttributeSymbol : CSharpSymbol {

        private CSharpCompilation m_compilation;
        private AttributeData m_data;
        private CSharpSymbol m_symbol;
        private CSharpSyntheticAttribute m_value;

        internal CSharpAttributeSymbol(CSharpCompilation compilation, CSharpSymbol symbol, AttributeData data)
            : base(compilation, null) {
            m_data = data;
            m_symbol = symbol;
            m_compilation = compilation;
        }

        public override CSharpSymbol DeclaringSymbol {
            get { return m_symbol; }
        }
        public CSharpTypeSymbol Type {
            get { return m_compilation.Get<CSharpTypeSymbol>(m_data.AttributeClass); }
        }
        public CSharpMethodSymbol Constructor {
            get { return m_compilation.Get<CSharpMethodSymbol>(m_data.AttributeConstructor); }
        }
        public IEnumerable<KeyValuePair<CSharpMemberSymbol, CSharpTypedConstant>> NamedArguments() {
            return m_data.NamedArguments.Select(o =>
                new KeyValuePair<CSharpMemberSymbol, CSharpTypedConstant>(
                    Type.GetMember(o.Key),
                    new CSharpTypedConstant(m_compilation, o.Value)
                )
            );
        }
        public IEnumerable<CSharpTypedConstant> Arguments() {
            return m_data.PositionalArguments.Select(o => new CSharpTypedConstant(m_compilation, o));
        }
        public dynamic Value {
            get {
                if (m_value == null)
                    m_value = new CSharpSyntheticAttribute(this);
                return m_value;
            }
        }
    }

    public struct CSharpTypedConstant {

        private CSharpCompilation m_compilation;
        private TypedConstant m_value;

        internal CSharpTypedConstant(CSharpCompilation compilation, TypedConstant value) {
            m_compilation = compilation;
            m_value = value;
        }

        public CSharpCompilation Compilation {
            get { return m_compilation; }
        }

        public bool IsString {
            get { return Type == Compilation.GetType(typeof(string)); }
        }
        public bool IsCharacter {
            get { return Type == Compilation.GetType(typeof(char)); }
        }
        public bool IsPrimitive {
            get { return m_value.Kind == TypedConstantKind.Primitive && !IsString; }
        }
        public bool IsEnum {
            get { return m_value.Kind == TypedConstantKind.Enum; }
        }
        public bool IsType {
            get { return m_value.Kind == TypedConstantKind.Type; }
        }
        public bool IsArray {
            get { return m_value.Kind == TypedConstantKind.Array; }
        }

        public CSharpTypeSymbol Type {
            get { return m_compilation.Get<CSharpTypeSymbol>(m_value.Type); }
        }
        public object Value {
            get {
                if (IsArray)
                    return Values;

                var typeSymbol = m_value.Value as NamedTypeSymbol;

                if (typeSymbol != null)
                    return Compilation.Get<CSharpTypeSymbol>(typeSymbol);

                return m_value.Value;
            }
        }
        public IEnumerable<CSharpTypedConstant> Values {
            get {
                if (!IsArray)
                    throw new InvalidOperationException();

                var compilation = m_compilation;

                return m_value.Values.Select(o => new CSharpTypedConstant(compilation, o));
            }
        }
        public int Length {
            get {
                if (!IsArray)
                    throw new InvalidOperationException();
                return m_value.Values.Count;
            }
        }
    }

    internal sealed class CSharpSyntheticAttribute : DynamicObject {

        private static IEnumerable<KeyValuePair<string, CSharpTypedConstant>> DynamicProperties(CSharpAttributeSymbol data) {

            // named arguments
            foreach (var namedArgument in data.NamedArguments()) {
                yield return new KeyValuePair<string, CSharpTypedConstant>(
                    namedArgument.Key.Name, namedArgument.Value);
            }

            // constructor arguments
            foreach (var constructorArgument in data.Constructor.Parameters()
                .Zip(data.Arguments(), (l, r) => new { Parameter = l, Argument = r })) {

                // capitalize the first character of the constructor parameter name
                var name = constructorArgument.Parameter.Name.ToUpperFirst();

                yield return new KeyValuePair<string, CSharpTypedConstant>(name, constructorArgument.Argument);
            }
        }
        private static dynamic GetDynamicPropertyValue(CSharpTypedConstant constant) {
            var compilation = constant.Compilation;

            // synthisize array and enum dynamic objects
            if (constant.IsArray) {

                // array type is object...
                var arrayType = typeof(object);

                // ...unless all elements are mscorlib primitives
                var elementType = constant.Type.ElementType;
                if (!elementType.IsEnum && elementType != compilation.TryGetType(typeof(object)))
                    arrayType = elementType.RuntimeType;

                // allocate the array and copy the values into it
                return constant.Values.Select(o => GetDynamicPropertyValue(o)).ToArray(arrayType);
            }

            // synthisized enum
            if (constant.IsEnum)
                return CSharpSyntheticEnum.Construct(constant.Value, constant.Type);

            // mscorlib primitve
            return constant.Value;
        }

        private CSharpAttributeSymbol m_data;
        private Dictionary<string, object> m_values;

        internal CSharpSyntheticAttribute(CSharpAttributeSymbol data) {
            m_data = data;
            m_values = new Dictionary<string, object>();

            foreach (var property in DynamicProperties(m_data)) {
                var name = property.Key;
                var constant = property.Value;

                m_values[name] = GetDynamicPropertyValue(constant);
            }
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result) {
            return m_values.TryGetValue(binder.Name, out result);
        }
    }

    internal sealed class CSharpSyntheticEnum : DynamicObject {

        internal static dynamic Construct(object e, CSharpTypeSymbol type) {
            var value = (long)(dynamic)e;
            var values = type.Fields().ToDictionary(o => o.Name, o => (long)o.RawConstantValue);
            return new CSharpSyntheticEnum(value, values);
        }

        private long m_value;
        private Dictionary<string, long> m_values;

        internal CSharpSyntheticEnum(long value, Dictionary<string, long> values) {
            m_value = value;
            m_values = values;
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result) {
            var name = binder.Name;
            var value = m_values.GetValueOrDefault(name);
            if (value == null)
                throw new MissingFieldException();

            result = m_value == value;
            return true;
        }
    }
}